/* src/interfaces/ecpg/ecpglib/data.c */

#define POSTGRES_ECPG_INTERNAL
#include "postgres_fe.h"

#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <math.h>

#include "ecpgtype.h"
#include "ecpglib.h"
#include "ecpgerrno.h"
#include "extern.h"
#include "sqlca.h"
#include "pgtypes_numeric.h"
#include "pgtypes_date.h"
#include "pgtypes_timestamp.h"
#include "pgtypes_interval.h"

/* returns true if character c is a delimiter for the given array type */
static bool array_delimiter(enum ARRAY_TYPE isarray, char c)
{
    if (isarray == ECPG_ARRAY_ARRAY && c == ',')
        return true;

    if (isarray == ECPG_ARRAY_VECTOR && c == ' ')
        return true;

    return false;
}

/* returns true if character c marks the boundary for the given array type */
static bool array_boundary(enum ARRAY_TYPE isarray, char c)
{
    if (isarray == ECPG_ARRAY_ARRAY && c == '}')
        return true;

    if (isarray == ECPG_ARRAY_VECTOR && c == '\0')
        return true;

    return false;
}

/* returns true if some garbage is found at the end of the scanned string */
static bool garbage_left(enum ARRAY_TYPE isarray, char* scan_length, enum COMPAT_MODE compat)
{
    /*
     * INFORMIX allows for selecting a numeric into an int, the result is
     * truncated
     */
    if (isarray == ECPG_ARRAY_NONE) {
        if (INFORMIX_MODE(compat) && *scan_length == '.')
            return false;

        if (*scan_length != ' ' && *scan_length != '\0')
            return true;
    } else if (ECPG_IS_ARRAY(isarray) && !array_delimiter(isarray, *scan_length) &&
               !array_boundary(isarray, *scan_length))
        return true;

    return false;
}

/* stolen code from src/backend/utils/adt/float.c */
#if defined(WIN32) && !defined(NAN)
static const uint32 nan[2] = {0xffffffff, 0x7fffffff};

#define NAN (*(const double*)nan)
#endif

static double get_float8_infinity(void)
{
#ifdef INFINITY
    return (double)INFINITY;
#else
    return (double)(HUGE_VAL * HUGE_VAL);
#endif
}

static double get_float8_nan(void)
{
    /* (double) NAN doesn't work on some NetBSD/MIPS releases */
#if defined(NAN) && !(defined(__NetBSD__) && defined(__mips__))
    return (double)NAN;
#else
    return (double)(0.0 / 0.0);
#endif
}

static bool check_special_value(char* ptr, double* retval, char** endptr)
{
    if (pg_strncasecmp(ptr, "NaN", 3) == 0) {
        *retval = get_float8_nan();
        *endptr = ptr + 3;
        return true;
    } else if (pg_strncasecmp(ptr, "Infinity", 8) == 0) {
        *retval = get_float8_infinity();
        *endptr = ptr + 8;
        return true;
    } else if (pg_strncasecmp(ptr, "-Infinity", 9) == 0) {
        *retval = -get_float8_infinity();
        *endptr = ptr + 9;
        return true;
    }

    return false;
}

bool ecpg_get_data(const PGresult* results, int act_tuple, int act_field, int lineno, enum ECPGttype type,
    enum ECPGttype ind_type, char* var, char* ind, long varcharsize, long offset, long ind_offset,
    enum ARRAY_TYPE isarray, enum COMPAT_MODE compat, bool force_indicator)
{
    struct sqlca_t* sqlca = ECPGget_sqlca();
    char* pval = (char*)PQgetvalue(results, act_tuple, act_field);
    int binary = PQfformat(results, act_field);
    int size = PQgetlength(results, act_tuple, act_field);
    int value_for_indicator = 0;
    long log_offset;

    /*
     * If we are running in a regression test, do not log the offset variable,
     * it depends on the machine's alignment.
     */
    if (ecpg_internal_regression_mode)
        log_offset = -1;
    else
        log_offset = offset;

    ecpg_log("ecpg_get_data on line %d: RESULT: %s offset: %ld; array: %s\n",
        lineno,
        pval != NULL ? (binary ? "BINARY" : pval) : "EMPTY",
        log_offset,
        ECPG_IS_ARRAY(isarray) ? "yes" : "no");

    /* pval is a pointer to the value */
    if (pval == NULL) {
        /*
         * This should never happen because we already checked that we found
         * at least one tuple, but let's play it safe.
         */
        ecpg_raise(lineno, ECPG_NOT_FOUND, ECPG_SQLSTATE_NO_DATA, NULL);
        return (false);
    }

    /* We will have to decode the value */

    /*
     * check for null value and set indicator accordingly, i.e. -1 if NULL and
     * 0 if not
     */
    if (PQgetisnull(results, act_tuple, act_field))
        value_for_indicator = -1;

    switch (ind_type) {
        case ECPGt_short:
        case ECPGt_unsigned_short:
            *((short*)(ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
        case ECPGt_int:
        case ECPGt_unsigned_int:
            *((int*)(ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
        case ECPGt_long:
        case ECPGt_unsigned_long:
            *((long*)(ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
#ifdef HAVE_LONG_LONG_INT
        case ECPGt_long_long:
        case ECPGt_unsigned_long_long:
            *((long long int*)(ind + ind_offset * act_tuple)) = value_for_indicator;
            break;
#endif /* HAVE_LONG_LONG_INT */
        case ECPGt_NO_INDICATOR:
            if (value_for_indicator == -1) {
                if (force_indicator == false) {
                    /*
                     * Informix has an additional way to specify NULLs note
                     * that this uses special values to denote NULL
                     */
                    ECPGset_noind_null(type, var + offset * act_tuple);
                } else {
                    ecpg_raise(lineno, ECPG_MISSING_INDICATOR, ECPG_SQLSTATE_NULL_VALUE_NO_INDICATOR_PARAMETER, NULL);
                    return (false);
                }
            }
            break;
        default:
            ecpg_raise(lineno, ECPG_UNSUPPORTED, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, ecpg_type_name(ind_type));
            return (false);
            break;
    }

    if (value_for_indicator == -1)
        return (true);

    /* let's check if it really is an array if it should be one */
    if (isarray == ECPG_ARRAY_ARRAY) {
        if (*pval != '{') {
            ecpg_raise(lineno, ECPG_DATA_NOT_ARRAY, ECPG_SQLSTATE_DATATYPE_MISMATCH, NULL);
            return (false);
        }

        switch (type) {
            case ECPGt_char:
            case ECPGt_unsigned_char:
            case ECPGt_varchar:
            case ECPGt_string:
                break;

            default:
                pval++;
                break;
        }
    }

    do {
        if (binary) {
            if (varcharsize == 0 || varcharsize * offset >= size)
                memcpy(var + offset * act_tuple, pval, size);
            else {
                memcpy(var + offset * act_tuple, pval, varcharsize * offset);

                if (varcharsize * offset < size) {
                    /* truncation */
                    switch (ind_type) {
                        case ECPGt_short:
                        case ECPGt_unsigned_short:
                            *((short*)(ind + ind_offset * act_tuple)) = size;
                            break;
                        case ECPGt_int:
                        case ECPGt_unsigned_int:
                            *((int*)(ind + ind_offset * act_tuple)) = size;
                            break;
                        case ECPGt_long:
                        case ECPGt_unsigned_long:
                            *((long*)(ind + ind_offset * act_tuple)) = size;
                            break;
#ifdef HAVE_LONG_LONG_INT
                        case ECPGt_long_long:
                        case ECPGt_unsigned_long_long:
                            *((long long int*)(ind + ind_offset * act_tuple)) = size;
                            break;
#endif /* HAVE_LONG_LONG_INT */
                        default:
                            break;
                    }
                    sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
                }
            }
            pval += size;
        } else {
            char* scan_length = NULL;
            numeric* nres = NULL;
            interval* ires = NULL;
            switch (type) {
                long res;
                unsigned long ures;
                double dres;
                date ddres;
                timestamp tres;

                case ECPGt_short:
                case ECPGt_int:
                case ECPGt_long:
                    res = strtol(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat)) {
                        ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type) {
                        case ECPGt_short:
                            *((short*)(var + offset * act_tuple)) = (short)res;
                            break;
                        case ECPGt_int:
                            *((int*)(var + offset * act_tuple)) = (int)res;
                            break;
                        case ECPGt_long:
                            *((long*)(var + offset * act_tuple)) = (long)res;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

                case ECPGt_unsigned_short:
                case ECPGt_unsigned_int:
                case ECPGt_unsigned_long:
                    ures = strtoul(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat)) {
                        ecpg_raise(lineno, ECPG_UINT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type) {
                        case ECPGt_unsigned_short:
                            *((unsigned short*)(var + offset * act_tuple)) = (unsigned short)ures;
                            break;
                        case ECPGt_unsigned_int:
                            *((unsigned int*)(var + offset * act_tuple)) = (unsigned int)ures;
                            break;
                        case ECPGt_unsigned_long:
                            *((unsigned long*)(var + offset * act_tuple)) = (unsigned long)ures;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

#ifdef HAVE_LONG_LONG_INT
#ifdef HAVE_STRTOLL
                case ECPGt_long_long:
                    *((long long int*)(var + offset * act_tuple)) = strtoll(pval, &scan_length, 10);
                    if (garbage_left(isarray, scan_length, compat)) {
                        ecpg_raise(lineno, ECPG_INT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    break;
#endif /* HAVE_STRTOLL */
#ifdef HAVE_STRTOULL
                case ECPGt_unsigned_long_long:
                    *((unsigned long long int*)(var + offset * act_tuple)) = strtoull(pval, &scan_length, 10);
                    if ((isarray && *scan_length != ',' && *scan_length != '}') ||
                        (!isarray && !(INFORMIX_MODE(compat) && *scan_length == '.') && *scan_length != '\0' &&
                            *scan_length != ' ')) /* Garbage left */
                    {
                        ecpg_raise(lineno, ECPG_UINT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    break;
#endif /* HAVE_STRTOULL */
#endif /* HAVE_LONG_LONG_INT */

                case ECPGt_float:
                case ECPGt_double:
                    if (isarray && *pval == '"')
                        pval++;

                    if (!check_special_value(pval, &dres, &scan_length))
                        dres = strtod(pval, &scan_length);

                    if (isarray && *scan_length == '"')
                        scan_length++;

                    if (garbage_left(isarray, scan_length, compat)) {
                        ecpg_raise(lineno, ECPG_FLOAT_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                        return (false);
                    }
                    pval = scan_length;

                    switch (type) {
                        case ECPGt_float:
                            *((float*)(var + offset * act_tuple)) = dres;
                            break;
                        case ECPGt_double:
                            *((double*)(var + offset * act_tuple)) = dres;
                            break;
                        default:
                            /* Cannot happen */
                            break;
                    }
                    break;

                case ECPGt_bool:
                    if (pval[0] == 'f' && pval[1] == '\0') {
                        if (offset == sizeof(char))
                            *((char*)(var + offset * act_tuple)) = false;
                        else if (offset == sizeof(int))
                            *((int*)(var + offset * act_tuple)) = false;
                        else
                            ecpg_raise(lineno, ECPG_CONVERT_BOOL, ECPG_SQLSTATE_DATATYPE_MISMATCH, NULL);
                        pval++;
                        break;
                    } else if (pval[0] == 't' && pval[1] == '\0') {
                        if (offset == sizeof(char))
                            *((char*)(var + offset * act_tuple)) = true;
                        else if (offset == sizeof(int))
                            *((int*)(var + offset * act_tuple)) = true;
                        else
                            ecpg_raise(lineno, ECPG_CONVERT_BOOL, ECPG_SQLSTATE_DATATYPE_MISMATCH, NULL);
                        pval++;
                        break;
                    } else if (pval[0] == '\0' && PQgetisnull(results, act_tuple, act_field)) {
                        /* NULL is valid */
                        break;
                    }

                    ecpg_raise(lineno, ECPG_CONVERT_BOOL, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                    return (false);
                    break;

                case ECPGt_char:
                case ECPGt_unsigned_char:
                case ECPGt_string: {
                    char* str = (char*)(var + offset * act_tuple);

                    if (varcharsize == 0 || varcharsize > size) {
                        strncpy(str, pval, size + 1);
                        /* do the rtrim() */
                        if (type == ECPGt_string) {
                            char* last = str + size;

                            while (last > str && (*last == ' ' || *last == '\0')) {
                                *last = '\0';
                                last--;
                            }
                        }
                    } else {
                        strncpy(str, pval, varcharsize);

                        if (varcharsize < size) {
                            /* truncation */
                            switch (ind_type) {
                                case ECPGt_short:
                                case ECPGt_unsigned_short:
                                    *((short*)(ind + ind_offset * act_tuple)) = size;
                                    break;
                                case ECPGt_int:
                                case ECPGt_unsigned_int:
                                    *((int*)(ind + ind_offset * act_tuple)) = size;
                                    break;
                                case ECPGt_long:
                                case ECPGt_unsigned_long:
                                    *((long*)(ind + ind_offset * act_tuple)) = size;
                                    break;
#ifdef HAVE_LONG_LONG_INT
                                case ECPGt_long_long:
                                case ECPGt_unsigned_long_long:
                                    *((long long int*)(ind + ind_offset * act_tuple)) = size;
                                    break;
#endif /* HAVE_LONG_LONG_INT */
                                default:
                                    break;
                            }
                            sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';
                        }
                    }
                    pval += size;
                } break;

                case ECPGt_varchar: {
                    struct ECPGgeneric_varchar* variable = (struct ECPGgeneric_varchar*)(var + offset * act_tuple);

                    variable->len = size;
                    if (varcharsize == 0)
                        strncpy(variable->arr, pval, variable->len);
                    else {
                        strncpy(variable->arr, pval, varcharsize);

                        if (variable->len > varcharsize) {
                            /* truncation */
                            switch (ind_type) {
                                case ECPGt_short:
                                case ECPGt_unsigned_short:
                                    *((short*)(ind + offset * act_tuple)) = variable->len;
                                    break;
                                case ECPGt_int:
                                case ECPGt_unsigned_int:
                                    *((int*)(ind + offset * act_tuple)) = variable->len;
                                    break;
                                case ECPGt_long:
                                case ECPGt_unsigned_long:
                                    *((long*)(ind + offset * act_tuple)) = variable->len;
                                    break;
#ifdef HAVE_LONG_LONG_INT
                                case ECPGt_long_long:
                                case ECPGt_unsigned_long_long:
                                    *((long long int*)(ind + ind_offset * act_tuple)) = variable->len;
                                    break;
#endif /* HAVE_LONG_LONG_INT */
                                default:
                                    break;
                            }
                            sqlca->sqlwarn[0] = sqlca->sqlwarn[1] = 'W';

                            variable->len = varcharsize;
                        }
                    }
                    pval += size;
                } break;

                case ECPGt_decimal:
                case ECPGt_numeric:
                    if (isarray && *pval == '"')
                        nres = PGTYPESnumeric_from_asc(pval + 1, &scan_length);
                    else
                        nres = PGTYPESnumeric_from_asc(pval, &scan_length);

                    /* did we get an error? */
                    if (nres == NULL) {
                        ecpg_log(
                            "ecpg_get_data on line %d: RESULT %s; errno %d\n", lineno, pval != NULL ? pval : "", errno);

                        if (INFORMIX_MODE(compat)) {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            nres = PGTYPESnumeric_new();
                            if (nres != NULL)
                                ECPGset_noind_null(ECPGt_numeric, nres);
                            else {
                                ecpg_raise(lineno, ECPG_OUT_OF_MEMORY, ECPG_SQLSTATE_ECPG_OUT_OF_MEMORY, NULL);
                                return (false);
                            }
                        } else {
                            ecpg_raise(lineno, ECPG_NUMERIC_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    } else {
                        if (isarray && *scan_length == '"')
                            scan_length++;

                        if (garbage_left(isarray, scan_length, compat)) {
                            free(nres);
                            ecpg_raise(lineno, ECPG_NUMERIC_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    pval = scan_length;

                    if (type == ECPGt_numeric)
                        PGTYPESnumeric_copy(nres, (numeric*)(var + offset * act_tuple));
                    else
                        PGTYPESnumeric_to_decimal(nres, (decimal*)(var + offset * act_tuple));

                    PGTYPESnumeric_free(nres);
                    break;

                case ECPGt_interval:
                    if (isarray && *pval == '"')
                        ires = PGTYPESinterval_from_asc(pval + 1, &scan_length);
                    else
                        ires = PGTYPESinterval_from_asc(pval, &scan_length);

                    /* did we get an error? */
                    if (ires == NULL) {
                        ecpg_log(
                            "ecpg_get_data on line %d: RESULT %s; errno %d\n", lineno, pval != NULL ? pval : "", errno);

                        if (INFORMIX_MODE(compat)) {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ires = (interval*)ecpg_alloc(sizeof(interval), lineno);
                            if (ires == NULL)
                                return (false);

                            ECPGset_noind_null(ECPGt_interval, ires);
                        } else {
                            ecpg_raise(lineno, ECPG_INTERVAL_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    } else {
                        if (isarray && *scan_length == '"')
                            scan_length++;

                        if (garbage_left(isarray, scan_length, compat)) {
                            free(ires);
                            ecpg_raise(lineno, ECPG_INTERVAL_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }
                    pval = scan_length;

                    PGTYPESinterval_copy(ires, (interval*)(var + offset * act_tuple));
                    free(ires);
                    break;

                case ECPGt_date:
                    if (isarray && *pval == '"')
                        ddres = PGTYPESdate_from_asc(pval + 1, &scan_length);
                    else
                        ddres = PGTYPESdate_from_asc(pval, &scan_length);

                    /* did we get an error? */
                    if (errno != 0) {
                        ecpg_log(
                            "ecpg_get_data on line %d: RESULT %s; errno %d\n", lineno, pval != NULL ? pval : "", errno);

                        if (INFORMIX_MODE(compat)) {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ECPGset_noind_null(ECPGt_date, &ddres);
                        } else {
                            ecpg_raise(lineno, ECPG_DATE_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    } else {
                        if (isarray && *scan_length == '"')
                            scan_length++;

                        if (garbage_left(isarray, scan_length, compat)) {
                            ecpg_raise(lineno, ECPG_DATE_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }

                    *((date*)(var + offset * act_tuple)) = ddres;
                    pval = scan_length;
                    break;

                case ECPGt_timestamp:
                    if (isarray && *pval == '"')
                        tres = PGTYPEStimestamp_from_asc(pval + 1, &scan_length);
                    else
                        tres = PGTYPEStimestamp_from_asc(pval, &scan_length);

                    /* did we get an error? */
                    if (errno != 0) {
                        ecpg_log("ecpg_get_data on line %d: RESULT %s; errno %d\n", lineno, pval, errno);

                        if (INFORMIX_MODE(compat)) {
                            /*
                             * Informix wants its own NULL value here instead
                             * of an error
                             */
                            ECPGset_noind_null(ECPGt_timestamp, &tres);
                        } else {
                            ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    } else {
                        if (isarray && *scan_length == '"')
                            scan_length++;

                        if (garbage_left(isarray, scan_length, compat)) {
                            ecpg_raise(lineno, ECPG_TIMESTAMP_FORMAT, ECPG_SQLSTATE_DATATYPE_MISMATCH, pval);
                            return (false);
                        }
                    }

                    *((timestamp*)(var + offset * act_tuple)) = tres;
                    pval = scan_length;
                    break;

                default:
                    ecpg_raise(lineno, ECPG_UNSUPPORTED, ECPG_SQLSTATE_ECPG_INTERNAL_ERROR, ecpg_type_name(type));
                    return (false);
                    break;
            }
            if (ECPG_IS_ARRAY(isarray)) {
                bool string = false;

                /* set array to next entry */
                ++act_tuple;

                /* set pval to the next entry */

                /*
                 * *pval != '\0' should not be needed, but is used as a safety
                 * guard
                 */
                for (;
                     *pval != '\0' && (string || (!array_delimiter(isarray, *pval) && !array_boundary(isarray, *pval)));
                     ++pval)
                    if (*pval == '"')
                        string = string ? false : true;

                if (array_delimiter(isarray, *pval))
                    ++pval;
            }
        }
    } while (*pval != '\0' && !array_boundary(isarray, *pval));

    return (true);
}
